From afdb3f5a3993bb3f048554388732f5a024f77f09 Mon Sep 17 00:00:00 2001 From: Emmanuele Bassi Date: Tue, 15 Apr 2008 22:22:44 +0000 Subject: [PATCH] =?utf8?q?Bug=20487375=20=E2=80=93=20gtkrecent=20apps=20po?= =?utf8?q?ll=20~/.recently-used.xbel=20every=205=20seconds?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 2008-04-15 Emmanuele Bassi Bug 487375 – gtkrecent apps poll ~/.recently-used.xbel every 5 seconds * gtk/gtkrecentmanager.c: Use GFileMonitor to check for changes in the recently used resources storage file, and drop the timeout-based poll. * ChangeLog: Fix attribution of the patch of the previous commit. svn path=/trunk/; revision=20004 --- ChangeLog | 17 +- gtk/gtkrecentmanager.c | 341 +++++++++++++++-------------------------- 2 files changed, 135 insertions(+), 223 deletions(-) diff --git a/ChangeLog b/ChangeLog index 9d4f11ff9f..a205dbd939 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,15 @@ +2008-04-15 Emmanuele Bassi + + Bug 487375 – gtkrecent apps poll ~/.recently-used.xbel + every 5 seconds + + * gtk/gtkrecentmanager.c: Use GFileMonitor to check for + changes in the recently used resources storage file, and + drop the timeout-based poll. + + * ChangeLog: Fix attribution of the patch of the previous + commit. + 2008-04-15 Emmanuele Bassi Bug 506062 – gtk_recent_manager_add_item does not detect mime @@ -7,8 +19,9 @@ * gtk/gtkrecentmanager.c: (gtk_recent_manager_add_item_query_info_cb), - (gtk_recent_manager_add_item): Use GIO to (asynchronously) query - the MIME type of the passed URI. + (gtk_recent_manager_add_item): Use GIO to (asynchronously) + query the MIME type of the passed URI (based on a patch by + Armin Burgmeier) 2008-04-11 Carlos Garnacho D:ChangeLog diff --git a/gtk/gtkrecentmanager.c b/gtk/gtkrecentmanager.c index af6dba1530..9d2b23f132 100644 --- a/gtk/gtkrecentmanager.c +++ b/gtk/gtkrecentmanager.c @@ -49,22 +49,12 @@ /* the file where we store the recently used items */ #define GTK_RECENTLY_USED_FILE ".recently-used.xbel" -/* a poll approximately every five seconds */ -#define POLL_DELTA 5 - /* return all items by default */ #define DEFAULT_LIMIT -1 /* keep in sync with xdgmime */ #define GTK_RECENT_DEFAULT_MIME "application/octet-stream" -typedef struct -{ - GSourceFunc func; - gpointer data; - GDestroyNotify notify; -} ThreadsDispatch; - typedef struct { gchar *name; @@ -100,21 +90,20 @@ struct _GtkRecentInfo gint ref_count; }; +#define GTK_RECENT_MANAGER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_RECENT_MANAGER, GtkRecentManagerPrivate)) + struct _GtkRecentManagerPrivate { gchar *filename; guint is_dirty : 1; - guint write_in_progress : 1; - guint read_in_progress : 1; gint limit; gint size; GBookmarkFile *recent_items; - - time_t last_mtime; - guint poll_timeout; + + GFileMonitor *monitor; }; enum @@ -126,36 +115,38 @@ enum PROP_SIZE }; -static void gtk_recent_manager_dispose (GObject *object); -static void gtk_recent_manager_finalize (GObject *object); - -static void gtk_recent_manager_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec); -static void gtk_recent_manager_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec); -static void gtk_recent_manager_add_item_query_info_cb (GObject *source_object, - GAsyncResult *res, - gpointer user_data); -static void gtk_recent_manager_changed (GtkRecentManager *manager); - -static void gtk_recent_manager_real_changed (GtkRecentManager *manager); -static gboolean gtk_recent_manager_poll_timeout (gpointer data); -static void gtk_recent_manager_set_filename (GtkRecentManager *manager, - const gchar *filename); - -static void build_recent_items_list (GtkRecentManager *manager); -static void purge_recent_items_list (GtkRecentManager *manager, - GError **error); - -static RecentAppInfo *recent_app_info_new (const gchar *app_name); -static void recent_app_info_free (RecentAppInfo *app_info); - -static GtkRecentInfo *gtk_recent_info_new (const gchar *uri); -static void gtk_recent_info_free (GtkRecentInfo *recent_info); +static void gtk_recent_manager_dispose (GObject *object); +static void gtk_recent_manager_finalize (GObject *object); +static void gtk_recent_manager_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gtk_recent_manager_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void gtk_recent_manager_add_item_query_info_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data); +static void gtk_recent_manager_monitor_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data); +static void gtk_recent_manager_changed (GtkRecentManager *manager); +static void gtk_recent_manager_real_changed (GtkRecentManager *manager); +static void gtk_recent_manager_set_filename (GtkRecentManager *manager, + const gchar *filename); + +static void build_recent_items_list (GtkRecentManager *manager); +static void purge_recent_items_list (GtkRecentManager *manager, + GError **error); + +static RecentAppInfo *recent_app_info_new (const gchar *app_name); +static void recent_app_info_free (RecentAppInfo *app_info); + +static GtkRecentInfo *gtk_recent_info_new (const gchar *uri); +static void gtk_recent_info_free (GtkRecentInfo *recent_info); static guint signal_changed = 0; @@ -201,40 +192,11 @@ gtk_recent_manager_error_quark (void) return g_quark_from_static_string ("gtk-recent-manager-error-quark"); } -static gboolean -threads_dispatch (gpointer data) -{ - ThreadsDispatch *dispatch = data; - gboolean res = FALSE; - - GDK_THREADS_ENTER (); - - if (!g_source_is_destroyed (g_main_current_source ())) - res = dispatch->func (dispatch->data); - - GDK_THREADS_LEAVE (); - - return res; -} - -static void -threads_free (gpointer data) -{ - ThreadsDispatch *dispatch = data; - - if (dispatch->notify) - dispatch->notify (dispatch->data); - - g_slice_free (ThreadsDispatch, dispatch); -} - static void gtk_recent_manager_class_init (GtkRecentManagerClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); - gtk_recent_manager_parent_class = g_type_class_peek_parent (klass); - gobject_class->set_property = gtk_recent_manager_set_property; gobject_class->get_property = gtk_recent_manager_get_property; gobject_class->dispose = gtk_recent_manager_dispose; @@ -254,7 +216,7 @@ gtk_recent_manager_class_init (GtkRecentManagerClass *klass) P_("Filename"), P_("The full path to the file to be used to store and read the list"), NULL, - (G_PARAM_CONSTRUCT_ONLY | G_PARAM_READABLE | G_PARAM_WRITABLE))); + (G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE))); /** * GtkRecentManager:limit * @@ -316,34 +278,19 @@ static void gtk_recent_manager_init (GtkRecentManager *manager) { GtkRecentManagerPrivate *priv; - ThreadsDispatch *dispatch; - - priv = g_type_instance_get_private ((GTypeInstance *) manager, - GTK_TYPE_RECENT_MANAGER); - manager->priv = priv; + gchar *filename; + + manager->priv = priv = GTK_RECENT_MANAGER_GET_PRIVATE (manager); priv->limit = DEFAULT_LIMIT; priv->size = 0; - - priv->is_dirty = FALSE; - priv->write_in_progress = FALSE; - priv->read_in_progress = FALSE; - - priv->filename = g_build_filename (g_get_home_dir (), - GTK_RECENTLY_USED_FILE, - NULL); - - dispatch = g_slice_new (ThreadsDispatch); - dispatch->func = gtk_recent_manager_poll_timeout; - dispatch->data = manager; - dispatch->notify = NULL; - priv->poll_timeout = g_timeout_add_seconds_full (G_PRIORITY_DEFAULT + 30, - POLL_DELTA, - threads_dispatch, - dispatch, - threads_free); - build_recent_items_list (manager); + /* this will take care of building the files list */ + filename = g_build_filename (g_get_home_dir (), + GTK_RECENTLY_USED_FILE, + NULL); + gtk_recent_manager_set_filename (manager, filename); + g_free (filename); } static void @@ -399,10 +346,13 @@ gtk_recent_manager_dispose (GObject *object) GtkRecentManager *manager = GTK_RECENT_MANAGER (object); GtkRecentManagerPrivate *priv = manager->priv; - if (priv->poll_timeout) + if (priv->monitor) { - g_source_remove (priv->poll_timeout); - priv->poll_timeout = 0; + g_signal_handlers_disconnect_by_func (priv->monitor, + G_CALLBACK (gtk_recent_manager_monitor_changed), + manager); + g_object_unref (priv->monitor); + priv->monitor = NULL; } G_OBJECT_CLASS (gtk_recent_manager_parent_class)->dispose (object); @@ -432,15 +382,12 @@ gtk_recent_manager_real_changed (GtkRecentManager *manager) if (priv->is_dirty) { GError *write_error; - struct stat stat_buf; /* we are marked as dirty, so we dump the content of our * recently used items list */ g_assert (priv->filename != NULL); - priv->write_in_progress = TRUE; - /* if no container object has been defined, we create a new * empty container, and dump it */ @@ -461,26 +408,6 @@ gtk_recent_manager_real_changed (GtkRecentManager *manager) g_error_free (write_error); } - priv->write_in_progress = FALSE; - - /* we have sync'ed our list with the storage file, so we - * update the file mtime in order to skip the timed check - * and spare us from a re-read. - */ - if (g_stat (priv->filename, &stat_buf) < 0) - { - filename_warning ("Unable to stat() the recently used resources file " - "at `%s': %s.", - priv->filename, - g_strerror (errno)); - - g_object_thaw_notify (G_OBJECT (manager)); - - return; - } - - priv->last_mtime = stat_buf.st_mtime; - /* mark us as clean */ priv->is_dirty = FALSE; } @@ -496,44 +423,28 @@ gtk_recent_manager_real_changed (GtkRecentManager *manager) g_object_thaw_notify (G_OBJECT (manager)); } -/* timed poll()-ing of the recently used resources file. - * an event-based system would be more efficient. - */ -static gboolean -gtk_recent_manager_poll_timeout (gpointer data) +static void +gtk_recent_manager_monitor_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data) { - GtkRecentManager *manager = GTK_RECENT_MANAGER (data); - GtkRecentManagerPrivate *priv = manager->priv; - struct stat stat_buf; - int stat_res; - - /* wait for the next timeout if we have a read/write in progress */ - if (priv->write_in_progress || priv->read_in_progress) - return TRUE; + GtkRecentManager *manager = user_data; - stat_res = g_stat (priv->filename, &stat_buf); - if (stat_res < 0) + switch (event_type) { - /* the file does not exist, yet, so we wait */ - if (errno == ENOENT) - return TRUE; - - filename_warning ("Unable to stat() the recently used resources file " - "at `%s': %s.", - priv->filename, - g_strerror (errno)); - - return TRUE; - } - - /* the file didn't change from the last poll, so we bail out */ - if (stat_buf.st_mtime == priv->last_mtime) - return TRUE; + case G_FILE_MONITOR_EVENT_CHANGED: + case G_FILE_MONITOR_EVENT_CREATED: + gtk_recent_manager_changed (manager); + break; - /* the file has been changed, hence we emit the "changed" signal */ - gtk_recent_manager_changed (manager); + case G_FILE_MONITOR_EVENT_DELETED: + break; - return TRUE; + default: + break; + } } static void @@ -541,37 +452,48 @@ gtk_recent_manager_set_filename (GtkRecentManager *manager, const gchar *filename) { GtkRecentManagerPrivate *priv; - ThreadsDispatch *dispatch; + GFile *file; + GError *error; g_assert (GTK_IS_RECENT_MANAGER (manager)); + priv = manager->priv; + g_free (priv->filename); + + if (priv->monitor) + { + g_signal_handlers_disconnect_by_func (priv->monitor, + G_CALLBACK (gtk_recent_manager_monitor_changed), + manager); + g_object_unref (priv->monitor); + priv->monitor = NULL; + } + if (!filename || filename[0] == '\0') return; - g_free (manager->priv->filename); + priv->filename = g_strdup (filename); + file = g_file_new_for_path (priv->filename); - if (manager->priv->poll_timeout) + error = NULL; + priv->monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, &error); + if (error) { - g_source_remove (manager->priv->poll_timeout); - manager->priv->poll_timeout = 0; + filename_warning ("Unable to monitor `%s': %s\n" + "The GtkRecentManager will not update its contents " + "if the file is changed from other instances", + priv->filename, + error->message); + g_error_free (error); } + else + g_signal_connect (priv->monitor, "changed", + G_CALLBACK (gtk_recent_manager_monitor_changed), + manager); - priv->filename = g_strdup (filename); + g_object_unref (file); - dispatch = g_slice_new (ThreadsDispatch); - dispatch->func = gtk_recent_manager_poll_timeout; - dispatch->data = manager; - dispatch->notify = NULL; - priv->poll_timeout = g_timeout_add_seconds_full (G_PRIORITY_DEFAULT + 30, - POLL_DELTA, - threads_dispatch, - dispatch, - threads_free); - - /* mark us clean, so that we can re-read the list - * of recently used resources - */ priv->is_dirty = FALSE; build_recent_items_list (manager); } @@ -584,14 +506,10 @@ gtk_recent_manager_set_filename (GtkRecentManager *manager, static void build_recent_items_list (GtkRecentManager *manager) { - GtkRecentManagerPrivate *priv; - struct stat stat_buf; - int stat_res; - gboolean res; + GtkRecentManagerPrivate *priv = manager->priv; GError *read_error; gint size; - priv = manager->priv; g_assert (priv->filename != NULL); if (!priv->recent_items) @@ -600,60 +518,41 @@ build_recent_items_list (GtkRecentManager *manager) priv->size = 0; } - stat_res = g_stat (priv->filename, &stat_buf); - if (stat_res < 0) - { - /* the file doesn't exists, so we bail out and wait for the first - * write operation - */ - - if (errno == ENOENT) - return; - else - { - filename_warning ("Attempting to read the recently used resources file " - "at `%s', but an error occurred: %s. Aborting.", - priv->filename, - g_strerror (errno)); - - return; - } - } - - /* record the last mtime, for later use */ - priv->last_mtime = stat_buf.st_mtime; - - priv->read_in_progress = TRUE; - /* the file exists, and it's valid (we hope); if not, destroy the container * object and hope for a better result when the next "changed" signal is * fired. */ read_error = NULL; - res = g_bookmark_file_load_from_file (priv->recent_items, - priv->filename, - &read_error); + g_bookmark_file_load_from_file (priv->recent_items, priv->filename, &read_error); if (read_error) { - filename_warning ("Attempting to read the recently used resources file " - "at `%s', but the parser failed: %s.", - priv->filename, - read_error->message); + /* if the file does not exist we just wait for the first write + * operation on this recent manager instance, to avoid creating + * empty files and leading to spurious file system events (Sabayon + * will not be happy about those) + */ + if (read_error->domain == G_FILE_ERROR && + read_error->code != G_FILE_ERROR_NOENT) + filename_warning ("Attempting to read the recently used resources " + "file at `%s', but the parser failed: %s.", + priv->filename, + read_error->message); g_bookmark_file_free (priv->recent_items); priv->recent_items = NULL; g_error_free (read_error); } - - size = g_bookmark_file_get_size (priv->recent_items); - if (priv->size != size) + else { - priv->size = size; - - g_object_notify (G_OBJECT (manager), "size"); + size = g_bookmark_file_get_size (priv->recent_items); + if (priv->size != size) + { + priv->size = size; + + g_object_notify (G_OBJECT (manager), "size"); + } } - priv->read_in_progress = FALSE; priv->is_dirty = FALSE; } -- 2.30.2